Displaying Lists and Grid-Based Collections in Android
Course: Android App Development
Topic: Displaying Lists and Grid-Based Collections
Course Link: Android App Development Course
Demo Link: Register for a Course Demo
Website: JustAcademy
1. Introduction to Lists and Grid-Based Collections
Lists and grid-based collections are common UI patterns in Android applications. They allow developers to display multiple items in an organized, scrollable, and interactive format. Examples include contact lists, product catalogs, photo galleries, shopping applications, music libraries, task managers, and student directories.
A list usually displays items in a single row or column, while a grid arranges items into multiple rows and columns. The choice depends on the type of information, screen size, and user experience requirements.
Real-World Examples
- Contact application: Displays names and phone numbers in a vertical list.
- Shopping application: Displays product images, prices, and names in a grid.
- Photo gallery: Displays images in a grid of thumbnails.
- Music application: Displays songs in a list or albums in a grid.
- Task management application: Displays saved tasks in a scrollable list.
- Food delivery application: Displays restaurants or food items in cards.
2. Understanding Different Collection Layouts
| Layout |
Description |
Common Use |
| Vertical list |
Items appear one below another. |
Contacts, messages, tasks |
| Horizontal list |
Items appear side by side and scroll horizontally. |
Categories, featured products |
| Fixed-column grid |
Displays a specified number of columns. |
Product catalogs, photo galleries |
| Adaptive grid |
Adjusts the number of columns according to available width. |
Responsive product collections |
| Staggered grid |
Allows items to have different heights or widths. |
Image discovery feeds |
3. Methods for Displaying Collections in Android
Android supports different approaches for displaying collections. The correct approach depends on whether the application uses the traditional View system or Jetpack Compose.
| Technology |
Components |
Best Use |
| Traditional Android Views |
RecyclerView, Adapter, ViewHolder, GridLayoutManager |
XML-based Android interfaces |
| Jetpack Compose |
LazyColumn, LazyRow, LazyVerticalGrid |
Modern declarative Kotlin interfaces |
| Simple layouts |
Column, Row, verticalScroll, horizontalScroll |
Small collections that fit simple UI requirements |
For large or dynamically changing collections, RecyclerView or Compose lazy layouts are generally preferred because they avoid creating and laying out every item at once.
4. Understanding RecyclerView
RecyclerView is a flexible Android View component used to display large datasets efficiently. It reuses item views as users scroll instead of repeatedly creating a completely new view for every visible item.
Main Components of RecyclerView
- RecyclerView: The container that displays the collection.
- Adapter: Connects the dataset to the item views.
- ViewHolder: Holds references to the views within each item layout.
- LayoutManager: Controls how items are arranged, such as vertically, horizontally, or in a grid.
- Dataset: Contains the information displayed in the collection.
How RecyclerView Works
- The application provides a dataset to the adapter.
- The RecyclerView requests item views as needed.
- The adapter creates ViewHolders when new item views are required.
- The adapter binds data to each ViewHolder.
- When the user scrolls, RecyclerView can reuse views that leave the visible area.
- The adapter updates the displayed content when the dataset changes.
5. Adding RecyclerView to an Android Project
In a Kotlin Android project, add the RecyclerView dependency to the app-level Gradle file. Use a version compatible with your project's AndroidX dependencies.
dependencies {
implementation("androidx.recyclerview:recyclerview:YOUR_COMPATIBLE_VERSION")
}
Replace the placeholder with a compatible library version and synchronize the project. RecyclerView is part of AndroidX and is commonly used with XML layouts.
6. Creating a Data Model for a List
A data model defines the information associated with each item. For example, a product list may contain an ID, product name, price, and image resource.
Product.kt
data class Product(
val id: Int,
val name: String,
val price: Double,
val imageResId: Int
)
Each Product object represents one item in the collection. The unique ID can help identify the product when updating the list or responding to user actions.
7. Creating an Item Layout for RecyclerView
Each RecyclerView item can use an XML layout that defines how the data should appear. Create a file named item_product.xml inside the res/layout directory.
item_product.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="12dp">
<ImageView
android:id="@+id/productImage"
android:layout_width="72dp"
android:layout_height="72dp"
android:contentDescription="@string/product_image"
android:scaleType="centerCrop" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="12dp"
android:orientation="vertical">
<TextView
android:id="@+id/productName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/productPrice"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="15sp" />
</LinearLayout>
</LinearLayout>
The ImageView displays the product image, while the TextViews display the product name and price. Define the product_image string resource in strings.xml, or replace it with an appropriate existing resource.
8. Creating a RecyclerView Adapter
The adapter connects the product dataset to the item layout. It creates ViewHolders and binds product information to the views.
ProductAdapter.kt
class ProductAdapter(
private val products: List<Product>,
private val onProductClick: (Product) -> Unit
) : RecyclerView.Adapter<ProductAdapter.ProductViewHolder>() {
class ProductViewHolder(
itemView: View
) : RecyclerView.ViewHolder(itemView) {
val image: ImageView =
itemView.findViewById(R.id.productImage)
val name: TextView =
itemView.findViewById(R.id.productName)
val price: TextView =
itemView.findViewById(R.id.productPrice)
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): ProductViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_product, parent, false)
return ProductViewHolder(view)
}
override fun onBindViewHolder(
holder: ProductViewHolder,
position: Int
) {
val product = products[position]
holder.image.setImageResource(product.imageResId)
holder.name.text = product.name
holder.price.text =
holder.itemView.context.getString(
R.string.product_price,
product.price
)
holder.itemView.setOnClickListener {
onProductClick(product)
}
}
override fun getItemCount(): Int = products.size
}
This example assumes the appropriate Android, RecyclerView, View, ImageView, and TextView imports are present. Add the product_price formatted string resource to strings.xml and ensure that each image resource exists.
Example String Resources
<resources>
<string name="product_image">Product image</string>
<string name="product_price">Price: %.2f</string>
</resources>
9. Displaying a Vertical List with RecyclerView
To display items vertically, configure the RecyclerView with a LinearLayoutManager.
Activity Kotlin Example
val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
val products = listOf(
Product(1, "Keyboard", 1499.0, R.drawable.keyboard),
Product(2, "Headphones", 1999.0, R.drawable.headphones),
Product(3, "Mouse", 799.0, R.drawable.mouse)
)
recyclerView.layoutManager =
LinearLayoutManager(this)
recyclerView.adapter = ProductAdapter(products) { product ->
Toast.makeText(
this,
"Selected: ${product.name}",
Toast.LENGTH_SHORT
).show()
}
Ensure that the example image resources exist in the appropriate drawable directory and that the Activity layout contains a RecyclerView with the ID recyclerView.
Activity Layout Example
<androidx.recyclerview.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:padding="8dp" />
10. Displaying a Grid with RecyclerView
A grid arranges items into multiple columns or rows. RecyclerView can display a grid by using GridLayoutManager.
Two-Column Grid Example
recyclerView.layoutManager =
GridLayoutManager(this, 2)
recyclerView.adapter =
ProductAdapter(products) { product ->
Toast.makeText(
this,
product.name,
Toast.LENGTH_SHORT
).show()
}
The second argument to GridLayoutManager specifies the number of columns for a vertical grid. A two-column layout is useful for product cards, category tiles, and photo thumbnails.
Horizontal Grid Example
recyclerView.layoutManager = GridLayoutManager(
this,
2,
RecyclerView.HORIZONTAL,
false
)
This configuration creates a horizontally scrolling grid with two rows. Use the orientation that best fits the screen design.
11. Understanding Jetpack Compose Lazy Lists
Jetpack Compose provides declarative components for building lists and grids. Lazy layouts compose and lay out items as needed for the visible viewport, making them suitable for large or dynamic datasets.
| Composable |
Purpose |
LazyColumn |
Vertically scrolling list. |
LazyRow |
Horizontally scrolling list. |
LazyVerticalGrid |
Vertically scrolling grid. |
LazyHorizontalGrid |
Horizontally scrolling grid. |
LazyVerticalStaggeredGrid |
Vertically scrolling grid with variable item heights. |
Unlike a regular Column or Row that may compose all provided children, lazy layouts create content as it becomes relevant to the viewport. This reduces unnecessary UI work for large collections.
12. Displaying a List with LazyColumn
LazyColumn is a Jetpack Compose component for displaying vertically scrolling items. It is useful for messages, contacts, tasks, and product lists.
Basic LazyColumn Example
@Composable
fun NameList() {
val names = listOf(
"Aarav",
"Priya",
"Rahul",
"Neha",
"Karan"
)
LazyColumn {
items(names) { name ->
Text(
text = name,
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
)
HorizontalDivider()
}
}
}
The items() function adds content for each name. The list scrolls vertically when the collection exceeds the available screen height.
Using Stable Keys
When a collection can change, stable unique keys help Compose associate each item's state with the correct data record rather than its current position.
data class Contact(
val id: Long,
val name: String,
val phone: String
)
@Composable
fun ContactList(contacts: List<Contact>) {
LazyColumn {
items(
items = contacts,
key = { contact -> contact.id }
) { contact ->
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Text(contact.name)
Text(contact.phone)
}
HorizontalDivider()
}
}
}
Each contact should have a stable, unique ID. Avoid using a mutable list position as the key when records may be inserted, removed, or reordered.
13. Displaying a Horizontal List with LazyRow
LazyRow displays items horizontally. It is commonly used for category selectors, featured products, movie collections, and horizontally scrolling cards.
LazyRow Example
@Composable
fun CategoryList() {
val categories = listOf(
"Electronics",
"Fashion",
"Books",
"Home",
"Sports"
)
LazyRow(
horizontalArrangement =
Arrangement.spacedBy(12.dp),
contentPadding = PaddingValues(16.dp)
) {
items(categories) { category ->
Surface(
shape = RoundedCornerShape(12.dp),
tonalElevation = 2.dp
) {
Text(
text = category,
modifier = Modifier.padding(
horizontal = 18.dp,
vertical = 12.dp
)
)
}
}
}
}
The horizontalArrangement property controls spacing between items, while contentPadding adds space around the collection.
14. Displaying a Grid with LazyVerticalGrid
LazyVerticalGrid displays items in a vertically scrolling grid. It is especially useful for product catalogs, photo galleries, category tiles, and dashboard cards.
Two-Column Compose Grid
@Composable
fun ProductGrid(products: List<Product>) {
LazyVerticalGrid(
columns = GridCells.Fixed(2),
contentPadding = PaddingValues(12.dp),
horizontalArrangement =
Arrangement.spacedBy(12.dp),
verticalArrangement =
Arrangement.spacedBy(12.dp)
) {
items(
items = products,
key = { product -> product.id }
) { product ->
Card(
modifier = Modifier.fillMaxWidth()
) {
Column(
modifier = Modifier.padding(12.dp)
) {
Text(
text = product.name,
style = MaterialTheme.typography.titleMedium
)
Text(
text = "₹${product.price}"
)
}
}
}
}
}
This example assumes the Product model from earlier sections. The grid displays two columns and maintains consistent spacing between cards.
15. Creating an Adaptive Grid
Fixed-column grids always use a specified number of columns. Adaptive grids calculate how many columns can fit based on the available width and a minimum item size.
Adaptive Grid Example
LazyVerticalGrid(
columns = GridCells.Adaptive(minSize = 140.dp),
contentPadding = PaddingValues(12.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
items(products, key = { it.id }) { product ->
ProductCard(product)
}
}
With GridCells.Adaptive, the layout can display more columns on wider screens and fewer columns on narrower screens. This is helpful when designing interfaces for phones, tablets, and resizable windows.
16. Creating a Staggered Grid
A staggered grid supports items with different heights or widths. It is useful for collections in which content has naturally varying dimensions, such as photo feeds and discovery pages.
Compose Staggered Grid Example
@Composable
fun PhotoStaggeredGrid(photos: List<Photo>) {
LazyVerticalStaggeredGrid(
columns = StaggeredGridCells.Adaptive(160.dp),
verticalItemSpacing = 8.dp,
horizontalArrangement =
Arrangement.spacedBy(8.dp),
contentPadding = PaddingValues(8.dp)
) {
items(
items = photos,
key = { photo -> photo.id }
) { photo ->
PhotoCard(photo)
}
}
}
This example assumes that a Photo model and PhotoCard composable have been defined. Add the appropriate imports and use a compatible version of Jetpack Compose that supports staggered grids.
17. Creating Reusable List and Grid Item Components
Reusable item components help maintain a consistent design across a collection. Instead of repeating the same layout code in every screen, define a composable or XML item layout that accepts a data model.
Reusable Product Card in Compose
@Composable
fun ProductCard(
product: Product,
onClick: (Product) -> Unit = {}
) {
Card(
modifier = Modifier
.fillMaxWidth()
.clickable { onClick(product) }
) {
Column(
modifier = Modifier.padding(12.dp)
) {
Text(
text = product.name,
style = MaterialTheme.typography.titleMedium
)
Spacer(modifier = Modifier.height(8.dp))
Text(text = "₹${product.price}")
}
}
}
The same component can be used inside a LazyColumn, LazyRow, or LazyVerticalGrid. The parent collection controls item arrangement, while ProductCard controls the presentation of an individual product.
18. Handling Item Clicks
Collections commonly allow users to select an item to view details, open another screen, or perform an action. Item click handling should be attached to the item or an appropriate interactive control.
Compose Click Example
@Composable
fun ClickableNameList(
names: List<String>,
onNameSelected: (String) -> Unit
) {
LazyColumn {
items(names) { name ->
Text(
text = name,
modifier = Modifier
.fillMaxWidth()
.clickable {
onNameSelected(name)
}
.padding(16.dp)
)
HorizontalDivider()
}
}
}
The callback allows the parent screen to decide what happens after a selection. For example, it may navigate to a detail screen or display a confirmation message.
19. Updating Collections Efficiently
Lists and grids often need to update when records are added, removed, edited, or filtered. In Compose, the UI should observe state so that relevant changes trigger recomposition.
State-Based List Example
@Composable
fun EditableNameList() {
var names by remember {
mutableStateOf(
listOf("Aarav", "Priya", "Rahul")
)
}
Column {
Button(
onClick = {
names = names + "New Contact"
}
) {
Text("Add Contact")
}
LazyColumn {
items(names) { name ->
Text(
text = name,
modifier = Modifier.padding(16.dp)
)
}
}
}
}
In this example, updating the state list causes Compose to update the displayed collection. For real applications, use stable IDs and an appropriate state-holder such as a ViewModel when data is shared across screens or must survive configuration changes.
20. Adding Search and Filtering
Search and filtering help users locate relevant items in large collections. A search field can filter the source dataset before the items are passed to the list or grid.
Compose Search Example
@Composable
fun SearchableContactList(contacts: List<Contact>) {
var query by remember {
mutableStateOf("")
}
val filteredContacts = contacts.filter { contact ->
contact.name.contains(
query,
ignoreCase = true
)
}
Column {
OutlinedTextField(
value = query,
onValueChange = { query = it },
label = { Text("Search contacts") },
modifier = Modifier.fillMaxWidth()
)
LazyColumn {
items(
items = filteredContacts,
key = { it.id }
) { contact ->
Text(
text = contact.name,
modifier = Modifier.padding(16.dp)
)
}
}
}
}
This example filters an in-memory list. For very large datasets, consider filtering at the database or repository level rather than processing the entire collection during every UI update.
21. Displaying Empty, Loading, and Error States
A collection screen should explain what is happening when data is unavailable. A blank screen may confuse users if the list is empty, data is loading, or a request has failed.
| State |
Recommended UI |
| Loading |
Show a progress indicator or loading placeholders. |
| Empty |
Display a message such as "No items available" and an optional action. |
| Error |
Explain the problem and provide a retry action when appropriate. |
| Success |
Display the list or grid of available records. |
Empty-State Example
@Composable
fun TaskCollection(tasks: List<Task>) {
if (tasks.isEmpty()) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text("No tasks available")
}
} else {
LazyColumn {
items(tasks, key = { it.id }) { task ->
Text(
text = task.title,
modifier = Modifier.padding(16.dp)
)
}
}
}
}
This example assumes a Task model with an ID and title. In a complete application, distinguish a genuinely empty dataset from a loading state or a failed data request.
22. Performance Optimization for Lists and Grids
Performance becomes important when collections contain hundreds or thousands of items. Efficient layouts, stable keys, appropriate images, and controlled state updates can help improve scrolling behavior.
- Use RecyclerView or lazy Compose layouts for large or dynamic collections.
- Provide stable, unique item keys when records can change position.
- Avoid performing expensive calculations inside each item during recomposition or binding.
- Use appropriately sized images and load remote images with an image-loading library.
- Provide predictable item dimensions when possible, especially for asynchronously loaded content.
- Avoid deeply nested scrolling containers with the same orientation unless their sizes and behavior are carefully controlled.
- Use pagination for very large or network-backed datasets.
- Use DiffUtil or ListAdapter for efficient RecyclerView dataset updates.
- Use suitable content types in heterogeneous Compose collections when it improves item reuse.
- Measure scrolling performance using realistic datasets and release builds.
23. Handling Large Collections with Pagination
Pagination loads data in smaller portions rather than loading the entire dataset at once. It is useful for social feeds, product catalogs, transaction histories, and server-backed search results.
Benefits of Pagination
- Reduces initial loading time.
- Limits memory usage.
- Reduces the amount of data requested at one time.
- Can improve the user experience for very large collections.
- Supports incremental loading as the user scrolls.
For Room-backed or remote datasets, the Android Paging library can work with RecyclerView and Jetpack Compose. Compose applications commonly use the Paging Compose integration and collect paged items into a lazy layout.
24. Accessibility and Responsive Design
Lists and grids should be easy to navigate for users with different accessibility needs and across a range of device sizes.
- Provide meaningful descriptions for informative images.
- Use readable text sizes and sufficient contrast.
- Ensure clickable items and buttons have appropriate touch targets.
- Use clear visual feedback for selected or pressed items.
- Do not communicate important status using color alone.
- Use adaptive grid sizing when a fixed number of columns would make items too narrow.
- Test layouts with larger font settings and different screen sizes.
- Keep focus order and semantics understandable for screen-reader users.
25. Common Problems and Solutions
| Problem |
Possible Cause |
Solution |
| RecyclerView displays no items |
Missing adapter, empty dataset, or incorrect view setup |
Check the adapter, data, layout manager, and item layout. |
| Grid shows an unexpected number of columns |
Incorrect GridLayoutManager span count |
Set the required span count and orientation. |
| Items show incorrect information |
Incorrect adapter binding or stale dataset |
Verify the binding logic and update mechanism. |
| Compose list does not update |
Data changed without updating observable state |
Use Compose state or collect a state stream. |
| Item state moves to the wrong row |
Unstable or positional keys |
Use stable, unique item IDs. |
| Scrolling becomes slow |
Heavy item layouts, image loading, or unnecessary recomposition |
Optimize item content and use lazy layouts. |
| Grid cards appear cramped |
Items are too narrow or spacing is inadequate |
Adjust column count, minimum cell width, and spacing. |
| Images load with unexpected dimensions |
Incorrect image scaling or missing size constraints |
Set appropriate dimensions and image scaling behavior. |
| App crashes with nested scrolling |
Unbounded nested lazy containers in the same direction |
Use a single lazy parent or constrain the nested container. |
26. Practical Mini Project: Product Catalog
Create a product catalog application that supports both list and grid layouts. Users should be able to browse products, search by name, and select a product to view additional details.
Project Requirements
- Display product names and prices.
- Show product images or image placeholders.
- Support a vertical list layout.
- Support a two-column or adaptive grid layout.
- Allow users to search products by name.
- Display a product detail view when an item is selected.
- Show a clear empty state when no products match the search.
- Use stable IDs for collection items.
- Maintain responsive spacing on different screen sizes.
Development Steps
- Create the Android project.
- Define a Product data model with a stable ID.
- Create sample product data or retrieve it from a repository.
- Build a reusable product card component.
- Implement a list using RecyclerView or LazyColumn.
- Implement a grid using GridLayoutManager or LazyVerticalGrid.
- Add a control to switch between list and grid modes.
- Implement search and filtering.
- Add item click handling and a product detail screen.
- Test scrolling, empty states, accessibility, and performance.
27. Practice Exercises
- Create a vertical list of ten student names using LazyColumn.
- Display a horizontal row of product categories using LazyRow.
- Create a two-column grid of products using LazyVerticalGrid.
- Implement the same product grid using RecyclerView and GridLayoutManager.
- Add item click handling and show the selected product's name.
- Use stable IDs to preserve item identity when the collection changes.
- Add a search field that filters contacts by name.
- Display an empty-state message when no search results are available.
- Build an adaptive grid that adjusts to different screen widths.
- Research Paging 3 and outline how it can support a large remote collection.
28. Interview Questions and Answers
Q1. What is RecyclerView?
Answer: RecyclerView is an Android View component for displaying collections efficiently by reusing item views as the user scrolls.
Q2. What is the role of an Adapter?
Answer: The adapter connects the dataset to the UI and creates or binds item views.
Q3. What is a ViewHolder?
Answer: A ViewHolder holds references to the views used by a collection item, reducing repeated view lookups.
Q4. What does a LayoutManager do?
Answer: It controls the arrangement and scrolling behavior of RecyclerView items.
Q5. What is the difference between LinearLayoutManager and GridLayoutManager?
Answer: LinearLayoutManager displays items in a single row or column, while GridLayoutManager arranges items across a specified number of rows or columns.
Q6. What is LazyColumn?
Answer: LazyColumn is a Jetpack Compose component that displays a vertically scrolling collection and composes items as needed.
Q7. What is LazyRow?
Answer: LazyRow displays a horizontally scrolling collection of items.
Q8. What is LazyVerticalGrid?
Answer: LazyVerticalGrid displays items in multiple columns within a vertically scrolling container.
Q9. What is the difference between fixed and adaptive grids?
Answer: A fixed grid uses a specified number of columns or rows. An adaptive grid adjusts how many cells fit based on available space and a minimum cell size.
Q10. Why are stable item keys important?
Answer: Stable keys help associate item state with the correct record when items are inserted, removed, or reordered.
Q11. How can large collections be optimized?
Answer: Use lazy layouts or RecyclerView, stable keys, efficient item layouts, image loading, pagination, and controlled state updates.
Q12. When should a developer use a staggered grid?
Answer: A staggered grid is useful when collection items have different heights or widths, such as images with varying aspect ratios.
29. Quick Revision Checklist
- Understand the difference between lists, grids, and staggered grids.
- Know the roles of RecyclerView, Adapter, ViewHolder, and LayoutManager.
- Use LinearLayoutManager for a single row or column.
- Use GridLayoutManager for traditional Android grid layouts.
- Use LazyColumn and LazyRow for Compose lists.
- Use LazyVerticalGrid and LazyHorizontalGrid for Compose grids.
- Choose fixed or adaptive grid sizing based on the design.
- Use stable unique keys for changing collections.
- Handle loading, empty, error, and success states.
- Optimize images and avoid unnecessary work inside collection items.
- Use pagination for large datasets when appropriate.
- Test accessibility, responsiveness, and scrolling performance.
Conclusion
Displaying lists and grid-based collections is an essential skill in Android app development. RecyclerView provides an efficient solution for traditional View-based interfaces, while Jetpack Compose offers lazy components such as LazyColumn, LazyRow, and LazyVerticalGrid for declarative interfaces. By choosing the correct layout, using stable item keys, handling state properly, optimizing performance, and designing accessible interfaces, developers can build responsive and maintainable collection screens.
Continue learning with the Android App Development course at JustAcademy, visit the JustAcademy website, or register for a course demo.
Additional learning resource: Official Android documentation: Lazy lists and lazy grids.